package org.github.manx98.jlibhac.tools.fssystem.romfs;

import cn.hutool.core.util.ByteUtil;
import org.github.manx98.jlibhac.common.math.IByteUtil;
import org.github.manx98.jlibhac.common.math.Uint32;
import org.github.manx98.jlibhac.fs.IStorage;
import org.github.manx98.jlibhac.fs.MemoryStorage;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteOrder;

/**
 * @author Manx98
 * @version 1.0
 * @since 2022/10/4 23:26
 */
public class RomFsDictionary<T> {
    private final int[] buckets;
    private final byte[] entries;
    private final int length;
    private final int capacity;
    private final int count;
    private final Class<T> valueType;
    private final int sizeOfEntry;
    private final int valueSize;

    public RomFsDictionary(IStorage bucketStorage, IStorage entryStorage, Class<T> valueType) throws IOException {
        this.buckets = (int[]) bucketStorage.toArray(int.class);
        this.entries = (byte[]) entryStorage.toArray(byte.class);
        this.length = entries.length;
        this.capacity = entries.length;
        this.valueType = valueType;
        this.valueSize = IByteUtil.sizeOf(valueType);
        this.sizeOfEntry = valueSize + IByteUtil.sizeOf(RomFsEntry.class);
        this.count = countEntries();
    }

    private int countEntries() {
        int count = 0;
        int nextStructOffset = (4 + valueSize) / 4;
        int[] data = new int[entries.length / 4];
        for (int i = 0; i < data.length; i++) {
            data[i] = ByteUtil.bytesToInt(entries, i * 4, ByteOrder.LITTLE_ENDIAN);
        }
        for (int bucket : buckets) {
            int next = bucket;
            while (next != -1) {
                next = data[next / 4 + nextStructOffset];
                count++;
            }
        }
        return count;
    }


    public EntryReferenceResult<T> tryGetValue(RomEntryKey key) throws IOException {
        return tryGetValue(getOffsetFromKey(key));
    }

    private EntryReferenceResult<T> tryGetValue(int offset) throws IOException {
        if (offset < 0 || offset + sizeOfEntry > entries.length) {
            throw new FileNotFoundException("invalid offset " + offset + ", offset must be between 0 and " + (entries.length - sizeOfEntry));
        }
        return getEntryReference(offset);
    }

    public String getValueReference(int offset, RomFsEntry<T> entry) {
        entry.Parent = ByteUtil.bytesToInt(entries, offset, ByteOrder.LITTLE_ENDIAN);
        IByteUtil.mapToStruct(entry, entries, offset);
        return new String(entries, offset + sizeOfEntry, entry.KeyLength);
    }

    static class EntryReferenceResult<T> {
        RomFsEntry<T> entry;
        String name;

        public EntryReferenceResult(RomFsEntry<T> entry, String name) {
            this.entry = entry;
            this.name = name;
        }
    }

    public int getOffsetFromKey(RomEntryKey key) throws IOException {
        Uint32 hashCode = key.getRomHashCode();
        int index = (int) (hashCode.longValue() % buckets.length);
        int i = buckets[index];

        while (i != -1) {
            EntryReferenceResult<T> reference = getEntryReference(i);

            if (key.Parent == reference.entry.Parent && key.Name.equals(reference.name)) {
                break;
            }

            i = reference.entry.Next;
        }

        return i;
    }

    public EntryReferenceResult<T> getEntryReference(int offset) throws IOException {
        RomFsEntry<T> entry = new RomFsEntry<>();
        entry.Parent = IByteUtil.bytesToInt(entries, offset, ByteOrder.LITTLE_ENDIAN);
        entry.Value = (T) (IByteUtil.mapType(valueType, new MemoryStorage(entries, offset + 4, valueSize), valueSize).value);
        entry.Next = IByteUtil.bytesToInt(entries, offset + valueSize + 4, ByteOrder.LITTLE_ENDIAN);
        entry.KeyLength = IByteUtil.bytesToInt(entries, offset + valueSize + 8, ByteOrder.LITTLE_ENDIAN);
        return new EntryReferenceResult<>(
                entry,
                new String(entries, offset + sizeOfEntry, entry.KeyLength)
        );
    }
}
